<?php
// +----------------------------------------------------------------------
// | Author: sunjiafu <sunjiafu@prope.com.cn>
// | Date: 2015年4月8日
// | php扩展下载地址  http://windows.php.net/downloads/pecl/snaps/redis/
// | 引用方法如\Lib\Redis::getInstance()->set('user','sunjiafu');
// | 也可以直接\Lib\Redis::getInstance()->getHandler($is_write)获取redis对象，加方法执行。
// | $is_write ：是否为写数据，如果是写数据为true;读为false;
// +----------------------------------------------------------------------
namespace Lib;
defined('THINK_PATH') or exit();

class Redis{
    
    private static $YN_RW_SEPARATE;  //是否开启主从
    
    private static $master_handler; // 主redis连接对象
    
    private static $slave_handler; //从redis连接对象
    
    function __construct(){
        
        self::$YN_RW_SEPARATE=C('REDIS_RW_SEPARATE'); //从配置文件中读取是否开启主从
        
    }
    
    /**
     * 获取redis连接配置信息
     */
    private static function getConfig($is_master = true)
    {
        
         $master = explode(':',C('REDIS_HOST'));
        
         $sleve = explode(':',C('REDIS_HOST_R'));
        
        if($is_master){
            $options['host'] = $master[0];
            $options['port'] = $master[1];
        }else{
            $options['host'] = $sleve[0];
            $options['port'] = $sleve[1];
        }
        
        return $options;
        
    }
    
    /**
     * 连接redis
     * @param array $options 配置文件
     * @return \Redis 数据库连接对象
     */
    private static function redisConnect($options){
        
        if ( !extension_loaded('redis') ) {
            E(L('_NOT_SUPPORT_').':redis');
        }
        
        $options = array_merge(array (
            'timeout'       => C('DATA_CACHE_TIMEOUT') ? : false,
            'persistent'    => false,
        ),$options);
        
        $handler  = new \Redis;
        
        $func = $options['persistent'] ? 'pconnect' : 'connect';
        
        $options['timeout'] === false ?
        
        $handler->$func($options['host'], $options['port']) :
        
        $handler->$func($options['host'], $options['port'], $options['timeout']);
        
        return $handler;
        
        
    }
    
    /**
     * 获取redis连接对象
     * @param bool $is_write 是否为写
     * @return Redis reids连接对象
     * 当开启主从和读的时候返回从redis连接对象，其他返回主连接对象。
     */
    private static function getHandler($is_write=false){
        
        if(self::$YN_RW_SEPARATE && !$is_write){
            
            if(empty(self::$slave_handler)) self::$slave_handler = self::redisConnect(self::getConfig(false));
            
            return self::$slave_handler;
            
        }else{
            
            if(empty(self::$master_handler)) self::$master_handler = self::redisConnect(self::getConfig(true));
            
            return self::$master_handler;
            
        }
        
    }
    
    /**
     * 关闭连接
     *
     * @param int $flag 关闭选择 0:关闭 Master 1:关闭 Slave 2:关闭所有
     * @return boolean
     */
    public function close($flag=2){
        
        switch ($flag){
            
            case '0' :

                self::getHandler(true)->close();   break;
            
            case '1' :
                
                self::getHandler()->close();   break;
                
            case '2':
                
                self::getHandler()->close();   
                
                self::getHandler()->close(true);
                
                break;
            
        }
        
        return true;
    }
    
    
    /**
     * 添空当前数据库
     *
     * @return boolean
     */
    public function clear(){
    
        return self::getHandler(true)->flushDB();
    
    }

    /**
     * @des 更换名称
     * @param string $src the key to rename
     * @param string $des the new name for the key
     */
    public function rename($src,$des)
    {
        //return self::getHandler(true)->rename($src,$des);
        //twemproxy 不支持
        return false;
    }
    
    /************************KV方法**********************************/
    
    /**
     * 写缓存
     *
     * @param string $key 组存KEY
     * @param string $value 缓存值
     * @param int $expire 过期时间， 0:表示无过期时间
     */
    public function set($key, $value, $expire=0){
        // 永不超时
        if($expire == 0){
            $ret = self::getHandler(true)->set($key, $value);
        }else{
            $ret = $this->getHandler(true)->setex($key, $expire, $value);
        }
        return $ret;
    }
    
    /**
     * 读缓存, 如果键不存在，则返回 FALSE
     * @param string $key 缓存KEY,支持一次取多个 $key = array('key1','key2')
     * @return string || boolean  失败返回 false, 成功返回字符串
     */
    public function get($key){
        // 是否一次取多个值
        $func = is_array($key) ? 'mGet' : 'get';
        
        return self::getHandler()->{$func}($key);
        
    }
    
    /**
     * 删除缓存
     *
     * @param string || array $key 缓存KEY，支持单个健:"key1" 或多个健:array('key1','key2')
     * @return int 删除的健的数量
     */
    public function delete($key){
    
        return self::getHandler(true)->delete($key);
    }
    
    /**
     * 条件形式设置缓存，如果 key 不存时就设置，存在时设置失败
     *
     * @param string $key 缓存KEY
     * @param string $value 缓存值
     * @return boolean
     */
    public function setnx($key, $value){
        
        return self::getHandler(true)->setnx($key, $value);
        
    }
    /**
     * 验证指定的键是否存在
     * @param string $key 键名
     * @param return 存在返回：TRUE;不存在返回：FALSE
     */
    public function exists($key){
        
        return self::getHandler()->exists($key);
        
    }
     

     
    /**
     * 值加加操作,类似 ++$i ,如果 key 不存在时自动设置为 0 后进行加加操作
     *
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int　操作后的值
     */
    public function incr($key,$default=1){
        if($default == 1){
            return self::getHandler(true)->incr($key);
        }else{
            return self::getHandler(true)->incrBy($key, $default);
        }
    }

    /**
     * 值加加操作,类似 ++$i ,如果 key 不存在时自动设置为 0 后进行加加操作
     *
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int　操作后的值
     */
    public function hIncrBy($name,$key,$default=1)
    {
        $redis = self::getHandler(true);

        return $redis->hIncrBy($name,$key,$default);
    }
     
    /**
     * 值减减操作,类似 --$i ,如果 key 不存在时自动设置为 0 后进行减减操作
     *
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int　操作后的值
     */
    public function decr($key,$default=1){
        if($default == 1){
            return self::getHandler(true)->decr($key);
        }else{
            return self::getHandler(true)->decrBy($key, $default);
        }
    }
     
    /**
     * 取得所有指定键的值。如果一个或多个键不存在，该数组中该键的值为假
     * @param array $keyArr 其中包含键值的列表数组
     * @param array 返回包含所有键的值的数组
     *废弃
     */
    // public function getMultiple($keyArr){
        
    //     if(!is_array($keyArr)) return false;
        
    //     return self::getHandler()->getMultiple($keyArr);
        
    // }
    
    
    //******************************list函数*******************************************//
    
    /**
     * 将一个值添加到列表的尾部
     * @param string $key 键名
     * @param stirng $value 键值
     */
    public function lpush($key,$value){

        return self::getHandler(true)->lpush($key,$value);
    
    }
    
    /**
     * 将一个值添加到列表的首部
     * @param string $key 键名
     * @param stirng $value 键值
     */
    public function rPush($key,$value){
    
        return self::getHandler(true)->rPush($key,$value);
    
    }
    
    /**
     * 返回的列表的长度。如果列表不存在或为空，该命令返回0。如果该键不是列表，该命令返回FALSE
     * @param string $key 集合名
     */
    public function lsize($key){
        
        return self::getHandler()->lsize($key);
        
    }
    
    /**
     * 返回指定键存储在列表中指定的元素。 0第一个元素，1第二个… -1最后一个元素，-2的倒数第二
     * @param string $key 序列名
     * @param int $num 第一个值，从0开始
     */
    public function lget($key,$num){
        
        return self::getHandler()->lget($key,$num);
        
    }
    
    /**
     * 为列表指定的索引赋新的值,若不存在该索引返回false.
     * @param string $key 列表名
     * @param int $num 序号 从0开始
     * @param string $value 新值
     */
    public function lset($key,$num,$value){
        
        return self::getHandler(true)->lset($key,$num,$value);
        
    }
    
    /**
     * 返回在该区域中的指定键列表中开始到结束存储的指定元素，lGetRange(key, start, end)。0第一个元素，1第二个元素… -1最后一个元素，-2的倒数第二
     * @param string $key 列表名
     * @param int $start 开始序号
     * @param int $end 结束序号
     */
    public function lgetrange($key,$start,$end){
        
        return self::getHandler()->lgetrange($key,$start,$end);
        
    }
    
    /**
     * 从列表中从头部开始移除count个匹配的值。如果count为零，所有匹配的元素都被删除。如果count是负数，内容从尾部开始删除。
     * @param stirng $key 键值名
     * @param stirng $value 值
     * @param int $count 数字
     */
    public function lrem($key,$value,$count=0){
        
        return self::getHandler(true)->lrem($key,$value,$count);
        
    }
    
    /**
     * 获取内容列表
     * @param string $key 键值名
     * @param int $start 开始条数
     * @param int $end 结束条数
     */
    public function lrange($key,$start=0,$end=10){
    
        return self::getHandler()->lrange($key,$start,$end);
    }
    
    /**
     * 从list首部弹出一个值
     * @param string $key list名
     */
    public function lpop($key){
    
        return self::getHandler(true)->lpop($key);
    
    }
    
    /**
     * 从list尾部弹出一个值
     * @param stirng $key list名
     */
    public function rpop($key){
    
        return self::getHandler(true)->rpop($key);
    
    }
    
    //**************************SET函数***************************************//
    /**
     *为一个Key添加一个值。如果这个值已经在这个Key中，则返回FALSE。
     *@param $key 集合名称
     *@param $value 集合值
     */
    public function sadd($key,$value){
    
        return self::getHandler(true)->sadd($key,$value);
    
    }
    
    /**
     * 获取集合中的列表
     * @param string $key 集合名
     */
    public function sMembers($key){
         
        return self::getHandler()->sMembers($key);
         
    }
    
    /**
     * 删除集合中的一个元素
     * @param string $key  集合名
     * @param string $value 值
     */
    public function sremove($key,$value){
         
        return self::getHandler(true)->sremove($key,$value);
    }
    
    
    
    /**
     * 将 集合名A中的value移动到集合名B中
     * @param string $srcKey 集合名A
     * @param string $dstKey 集合名B
     * @param string $member 集合A中的值
     */
    public function smove($srcKey,$dstKey,$value){
        
        return self::getHandler(true)->smove($srcKey,$dstKey,$value);
        
    }
    
    /**
     * 检查集合中是否存在指定的值
     * @param string $key 集合名
     * @param string $value 集合值
     * @return bool 
     */
    public function scontains($key,$value){
        
        return self::getHandler()->scontains($key,$value);
        
    }
    
    /**
     * 返回集合中存储值的数量
     * @param string $key 集合名称
     * @return int 
     */
    public function ssize($key){
        
        return self::getHandler()->ssize($key);
        
    }
    
    /**
     * 随机移除并返回key中的一个值
     * @param string $key 集合名
     * @return string 返回值
     */
    public function spop($key){

        return self::getHandler(true)->spop($key);
        
    }
    
    /**
     * 返回一个所有指定键的交集。如果只指定一个键，那么这个命令生成这个集合的成员。如果不存在某个键，则返回FALSE。
     * @param string $keyA 集合名
     * @param string $keyB 集合名
     * @return 集合
     */
    public function sinter($keyA,$keyB){
        
        return self::getHandler()->sinter($keyA,$keyB);
        
    }
    
    /*将给定集合之间的将领存储在指定的集合中，如果指定的集合已经存在，则将其覆盖
     * @param string $key 新的集合名
     * @param string $keyA 集合名
     * @param string $keyB 集合名
     * @return 新集合
     * 
     * */
    public function sinterstore($key,$keyA,$keyB)
    {
    	return self::getHandler()->sinterstore($key,$keyA,$keyB);
    }
    
	
    /**
     * 返回集合大小
     * @param string $key 集合名称
     */
	public function scard($key){
	    
	    return self::getHandler(true)->scard($key);
	    
	}
	
	//********************************hash****************************************//
	
    /**
     * 向名称为$name的hash中添加元素$kye值为$value
     * @param string $name 哈希表名
     * @param string $key  列名
     * @param string $value 值
     */
    public function hset($name,$key,$value){
        
        if(is_array($value)){

            return self::getHandler(true)->hset($name,$key,serialize($value));
            
        }
        
        return self::getHandler(true)->hset($name,$key,$value);
    }
    
    /**
     * 将数组插入hash中
     * @param string $name hash名
     * @param array $valueArr 数组
     * @return number 插入数
     */
    public function hmset($name,$valueArr){
        
        if(is_array($valueArr) && !empty($valueArr))
            
            return self::getHandler()->hmset($name,$valueArr);
        
        else
            
            return false;
        
    }
    
    /**
     * 返回名称为$name的hash中$key对应的value值
     * @param string $name hash表名
     * @param string $key 表值
     * @param bool $serialize 是否需要反序列号
     */
    public function hget($name,$key = null,$serialize=true){
        if($key){
            $row = self::getHandler()->hget($name,$key);
            if($row && $serialize){
                
                $row = unserialize($row);
                
            }
            return $row;
        }
        return self::getHandler()->hgetAll($name);
    }
    
    /**
     * 查看hash中name是否存在
     * @param string $name hash名
     * @param string $key hash键值
     */
    public function hExists($name,$key){
        
        return self::getHandler()->hExists($name,$key);
        
    }
    
    /**
     * 返回该列表中元素的个数
     * @param string $key hash名
     */
    public function hlen($key){
        
        return self::getHandler()->hlen($key);
        
    }
    
    /**
     * 删除名称为h的hash中键为key1的域
     * @param string $name hash名
     * @param string $key 键名
     */
    public function hdel($name,$key = null){
        if($key){
            return self::getHandler(true)->hdel($name,$key);
        }
        return self::getHandler(true)->hdel($name);
    }
    
    //********************************sorted set****************************************//
    /*
     * 有序集合
    * @param string $key 名称
    * @param score 积分
    * @param $mem  成员名
    * */
    public function zadd($key,$score,$mem)
    {
    	return self::getHandler(true)->zadd($key,$score,$mem);
    }
    /*
     * 有序集合
     * @param string $key 名称
     * @param start 开始下标
     * @param end 结束下标
     * */
    public function zrange($key, $start=0, $end=-1, $withscores=false)
    {
        return self::getHandler()->ZRANGE($key, $start, $end, $withscores);
    }
    
    /*
     * 有序集合 倒序排列
    * @param string $key 名称
    * @param start 开始下标
    * @param end 结束下标
    * */
    public function zrevrange($key,$start=0,$end=-1)
    {
    	return self::getHandler()->ZREVRANGE($key,$start,$end, WITHSCORES);
    }
    
    /*
     * 获取下标值 
    * @param string $key 名称
    * @param start 开始下标
    * @param end 结束下标
    * */
    public function zrank($key,$mem)
    {
    	return self::getHandler()->zrank($key,$mem);
    }
    
    /*
     * 获取下标值
    * @param string $key 名称
    * @param start 开始下标
    * @param end 结束下标
    * */
    public function zrevrank($key,$mem)
    {
    	return self::getHandler()->zrevrank($key,$mem);
    }
    /*
     * 计算有序集合的成员数
    * */
    public function zcard($key)
    {
    	return self::getHandler()->zcard($key);
    }
    
    /**
     * 删除有序集合中的指定memeber
     * @param string $key
     * @param string $member
     */
    public function zRem($key, $member){
        return self::getHandler(true)->zRem($key, $member);
    }
    
    /**********************事物*****************************/
    /**
     * 事物开始
     */
    public function multi(){
        return self::getHandler(true)->multi();
    }
    /**
     * 事物结束
     */
    
    public function exec(){
        return self::getHandler(true)->exec();
    }
    
    /* =================== 单例方法 =================== */
     
    public static function getInstance()
    {
        static $class = NULL;
        
        if ($class == NULL)
        {
            $class = new self();
        }
        return $class;
    }

    /**
     * 设置过期时间
     * @param string $key
     * @param integer $ttl
     */
    public function expire($key, $ttl=3600){
        return self::getHandler()->expire($key, $ttl);
    }

    //获取锁(单锁)
    //返回获取锁是否成功
    public function lock($key,$expire=60)
    {
        $redis = self::getHandler(true);
        if(empty($redis)){
            return false;
        }

        //生成key
        $key = 'RedisLock_'.trim($key);

        return $redis->set($key,1,array('nx','ex'=>$expire));
    }

    //清除锁
    public function clearLock($key)
    {
        $key = trim($key);
        $this->delete("RedisLock_{$key}"); //key

        return true;
    }

    //获取数值锁(多锁)(进行最大数值限制)
    //如果获取成功，则返回当前锁定的数量, 如果失败，则返回0
    public function lockNum($key,$num)
    {
        $num = (int)$num;
        $key = trim($key);
        if($num<1 || empty($key)){
            return false;
        }

        $redis = self::getHandler(true);
        if(empty($redis)){
            return false;
        }

        //$time1 = gettimeofday(true);

        $script = "
            local key = KEYS[1]
            local pkey = 'RedisLockProtect_{$key}'
            local num = {$num}

            if not redis.call('SET',pkey,1,'PX',500,'NX') then
                return 0
            end

            local n = redis.call('GET',key)
            if n and tonumber(n)>=num then
                return 0
            end

            n = redis.call('INCR',key)
            if n and n>num then
                redis.call('DECR',key)
                n = 0
            end

            redis.call('DEL',pkey)

            return n
       ";

        $i = $redis->eval($script,array("RedisLock_{$key}"),1);

        //echo number_format(gettimeofday(true)-$time1,'6','.','');

        return $i;
    }

    //释放锁(单锁, 多锁)
    //说明: 在获取锁执行完业务逻辑后，可以调用此方法主动释锁
    public function unLock($key)
    {
        //生成key
        $key = trim($key);

        $redis = self::getHandler(true);
        if(empty($redis)){
            return false;
        }

        $script = "
            local key = KEYS[1]
            local pkey = 'RedisLockProtect_{$key}'

            if not redis.call('SET',pkey,1,'PX',500,'NX') then
                return 0
            end

            local n = redis.call('GET',key)
            if n and tonumber(n)<=0 then
                return 0
            end

            n = redis.call('DECR',key)
            if n and n<0 then
                n = redis.call('INCR',key)
            end

            redis.call('DEL',pkey)

            return n
       ";

        return $redis->eval($script,array("RedisLock_{$key}"),1);
    }

    //获取锁key的值
    public function lockVal($key)
    {
        //生成key
        $key = 'RedisLock_'.trim($key);

        return $this->get($key);
    }

	//返回名称为key的zset中元素val的score
    public function zScore($key, $val)
    {
        return self::getHandler()->zScore($key, $val);
    }
}